#ifndef CONF_PARSER_H_
#define CONF_PARSER_H_

#include <glibmm.h>
#include <boost/utility.hpp>
#include <stack>

#include "version.h"

namespace gpyp {
namespace conf {

class Document;

class Source : boost::noncopyable {
private:
  static const char BOM_UTF8[];

private:
  char   *data_;
  size_t  capacity_;
  size_t  size_;

  void normalize();

public:
  inline const char *data() const {
    return data_;
  }
  inline size_t size() const {
    return size_;
  }

  void clear();
  bool set(const char    *data,
           size_t         size,
           const char    *charset,
           Glib::ustring &error_message);
  bool set(const char *data, size_t size);

  operator bool() const;
  bool operator!() const;

  Source();
  ~Source();
};

#if GLIB_CHECK_VERSION(2, 16, 0)
# define GMARKUP_PARSER_HAS_STACK
#endif

class ParserBase : boost::noncopyable {
private:
  GMarkupParser        parser_;
  GMarkupParseContext *context_;

#ifndef GMARKUP_PARSER_HAS_STACK
  GSList              *element_stack_;
#endif

  static void _on_start_element(GMarkupParseContext *context,
                                const gchar         *element_name,
                                const gchar        **attribute_names,
                                const gchar        **attribute_values,
                                gpointer             user_data,
                                GError             **error);
  static void _on_end_element  (GMarkupParseContext *context,
                                const gchar         *element_name,
                                gpointer             user_data,
                                GError             **error);
  static void _on_error        (GMarkupParseContext *context,
                                GError              *error,
                                gpointer             user_data);

#ifndef GMARKUP_PARSER_HAS_STACK
  void free_element_stack();
#endif

  bool is_path(const GSList *&stack, const char *path) const;

protected:
  bool is_path(const char *path) const;
  inline bool is_path(const Glib::ustring &path) const {
    return is_path(path.c_str());
  }

  inline virtual void on_start_parsing() {
  }
  inline virtual void on_complete_parsing() {
  }

  inline virtual void on_start_element(const char  */*element_name*/,
                                       const char **/*attribute_names*/,
                                       const char **/*attribute_values*/,
                                       GError     **/*error*/) {
  }
  inline virtual void on_end_element(const char */*element_name*/,
                                     GError    **/*error*/) {
  }
  inline virtual void on_error(GError */*error*/) {
  }

public:
  bool parse(const Source &source);
  bool parse(const std::string &file_path);

protected:
  ParserBase();
  virtual ~ParserBase();
};

class ConfParser : public ParserBase {
private:
  Document &document_;

  boost::shared_ptr<std::stack<Element *> > stack_;
  Element *current_;

  virtual void on_start_parsing();

  virtual void on_start_element(const char  *element_name,
                                const char **attribute_names,
                                const char **attribute_values,
                                GError     **error);
  virtual void on_end_element  (const char  *element_name,
                                GError     **error);
  virtual void on_error        (GError      *error);

public:
  inline Document &get_document() {
    return document_;
  }

  void push(Element     *element,
            const char  *element_name,
            const char **attribute_names,
            const char **attribute_values);
  void pop();

public:
  ConfParser(Document &document);
};

class AttributesWrapper : boost::noncopyable {
private:
  const char **names_;
  const char **values_;
  size_t       size_;

public:
  inline size_t size() const {
    return size_;
  }

  inline const char *name(size_t idx) const {
    return idx >= size_ ? NULL : names_[idx];
  }
  inline const char *value(size_t idx) const {
    return idx >= size_ ? NULL : values_[idx];
  }

  inline const char *get(const char *name) const {
    for(size_t i = 0; i < size_; ++i) {
      if (strcmp(names_[i], name) == 0) return values_[i];
    }
    return NULL;
  }
  inline int get(const char *name, int def) const {
    const char *val = get(name);
    return val ? toint(val, def) : def;
  }

  inline const char *operator[](const char *name) const {
    return get(name);
  }

  inline AttributesWrapper(const char **names, const char **values) :
    names_ (names),
    values_(values),
    size_  (MIN(g_strv_length((gchar **)names), g_strv_length((gchar **)values)))
  {
  }
};

class StackParser;
class StackParserContext : boost::noncopyable {
  friend class StackParser;
public:
  typedef sigc::slot<void, const AttributesWrapper &> OnLoad;
  typedef sigc::slot<void, const char *> OnAttr;
  typedef sigc::slot<void> UnLoad;
  typedef StackParserContext Context;

  struct Attr {
    OnAttr onattr_;
    inline Attr(OnAttr onattr) :
      onattr_(onattr)
    {
    }
  };

private:
  //Glib::ustring name_;

  OnLoad        onload_;
  UnLoad        unload_;

  Context      *parent_;
  GHashTable   *children_;
  GHashTable   *attrs_;

  size_t        depth_;

private:
  inline static void free_context(Context *context) {
    delete context;
  }
  inline static void free_attr(Attr *attr) {
    delete attr;
  }

public:
  inline Context *add(const char *name,
                      OnLoad      onload = OnLoad(),
                      UnLoad      unload = UnLoad()) {
    Context *c = find(name);
    if (!c) {
      c = new Context();
      c->onload_ = onload;
      c->unload_ = unload;
      c->parent_ = this;
      if (!children_) {
        children_ = g_hash_table_new_full(g_str_hash,
                                          g_str_equal,
                                          g_free,
                                          (GDestroyNotify)Context::free_context);
      }
      g_hash_table_insert(children_, g_strdup(name), c);
    }
    return c;
  }
  inline void add_attr(const char *name, OnAttr onattr) {
    Attr *a = find_attr(name);
    if (!a) {
      if (!attrs_) attrs_ = g_hash_table_new_full(g_str_hash,
                                                  g_str_equal,
                                                  g_free,
                                                  (GDestroyNotify)Context::free_attr);
      g_hash_table_insert(attrs_, g_strdup(name), new Attr(onattr));
    }
  }

  inline Context *find(const char *name) const {
    if (children_) return (Context *)g_hash_table_lookup(children_, name);
    return NULL;
  }
  inline Attr *find_attr(const char *name) const {
    if (attrs_) return (Attr *)g_hash_table_lookup(attrs_, name);
    return NULL;
  }

  inline void onload(const AttributesWrapper &attrs) {
    if (onload_) onload_(attrs);
  }
  inline void unload() {
    if (unload_) unload_();
  }
  inline void onattr(const AttributesWrapper &attrs) {
    if (attrs_) {
      for(size_t i = 0; i < attrs.size(); ++i) {
        Attr *a = find_attr(attrs.name(i));
        if (a) a->onattr_(attrs.value(i));
      }
    }
  }

  inline StackParserContext() :
    parent_  (NULL),
    children_(NULL),
    attrs_   (NULL),
    depth_   (0)
  {
  }
  inline ~StackParserContext() {
    if (children_) g_hash_table_destroy(children_);
    if (attrs_)    g_hash_table_destroy(attrs_);
  }
};

class StackParser : public ParserBase {
public:
  typedef conf::AttributesWrapper AttributesWrapper;
  typedef conf::StackParserContext Context;

protected:
  Context  root_context_;
  Context *current_context_;

protected:
  inline virtual void on_start_parsing() {
    current_context_ = &root_context_;
    current_context_->depth_ = 0;
  }

private:
  inline virtual void on_start_element(const char  *element_name,
                                       const char **attribute_names,
                                       const char **attribute_values,
                                       GError     **error) {
    Context *c = NULL;
    if (current_context_->depth_ == 0 &&
        (c = current_context_->find(element_name))) {
      c->depth_ = 0;
      AttributesWrapper attrs(attribute_names, attribute_values);
      try {
        c->onload(attrs);
      } catch(const std::exception &ex) {
        g_set_error(error, 0, 0, "%s", ex.what());
      } catch(const Glib::Exception &ex) {
        g_set_error(error, 0, 0, "%s", ex.what().c_str());
      } catch(...) {
        g_set_error(error, 0, 0, "Unknown error");
      }
      c->onattr(attrs);
      current_context_ = c;
    } else {
      ++current_context_->depth_;
    }
  }
  inline virtual void on_end_element(const char */*element_name*/,
                                     GError    **error) {
    if (current_context_->depth_ == 0) {
      try {
        current_context_->unload();
      } catch(const std::exception &ex) {
        g_set_error(error, 0, 0, "%s", ex.what());
      } catch(const Glib::Exception &ex) {
        g_set_error(error, 0, 0, "%s", ex.what().c_str());
      } catch(...) {
        g_set_error(error, 0, 0, "Unknown error");
      }
      current_context_ = current_context_->parent_;
    } else {
      --current_context_->depth_;
    }
  }

protected:
  inline Context *add(const char     *name,
                      Context::OnLoad onload = Context::OnLoad(),
                      Context::UnLoad unload = Context::UnLoad()) {
    return root_context_.add(name, onload, unload);
  }

  inline StackParser() :
    current_context_(NULL)
  {
  }
};

}/*conf*/
}/*gpyp*/

#endif/*CONF_PARSER_H_*/
